Skip to content

feat: closeable info alerts on Sequence Properties (session-only)#13

Merged
PaulNewling merged 12 commits into
mainfrom
paulnewling/fix/dismissable-info-messages-minimal
May 29, 2026
Merged

feat: closeable info alerts on Sequence Properties (session-only)#13
PaulNewling merged 12 commits into
mainfrom
paulnewling/fix/dismissable-info-messages-minimal

Conversation

@PaulNewling
Copy link
Copy Markdown
Collaborator

@PaulNewling PaulNewling commented May 27, 2026

What

Info-message alerts on the Main tab now have a close button. Dismissals follow the firing: hidden while the message is in the workflow output, surface fresh if the workflow stops emitting it and later re-emits.

Why

Workflow advisories (vhh(), peptidesShortInstability(), partialChainMissingFullChain(), etc.) render on every Main view with no way to acknowledge. For inputs that legitimately fire an advisory across many runs, users re-see the same banner on every navigation. This PR adds the close affordance and ties dismissal to the firing so a different input that re-fires the same string shows fresh instead of staying silently suppressed.

How

State lives in a module-scope ref<Set<string>> in app.ts:

  • PlAlert close emit → dismissInfoMessage(m) adds to the set.
  • visibleInfoMessages computed filters outputs.info?.messages by the set.
  • watch(() => outputs.info?.messages, ...) prunes the set to the intersection with the current output on every re-emit.

Hairpin-safe: writes are user gestures and output → local-ref, both sanctioned by harnesses/block-dev/hairpin.md. No BlockData change, no migration.

Lifetime

Dismissals live as long as the block UI's JS context.

  • Survive: in-block navigation, switching to ≤3 other blocks and back (desktop LRU cache, limit 4), workflow re-runs where the message keeps firing.
  • Reset: switching to ≥4 other blocks and back (LRU eviction), project close, block reload, app restart.
  • Prune (not reset): workflow re-runs where a previously-dismissed message stops firing — entry drops from the set, a later re-fire surfaces fresh.

PR #12 prototyped a server-persisted alternative — closed because it gave "dismissed once, dismissed forever" semantics across all future workflow runs that re-emitted the string.

Test plan

  • pnpm build:dev clean
  • Dismiss alert → switch within-block tabs → return → still hidden
  • Dismiss alert → switch input so advisory stops firing → switch back → advisory shows fresh (auto-prune)
  • Close and reopen project → advisory shows fresh

Greptile Summary

This PR adds a close button to info-message alerts on the Main tab, with session-only dismissal semantics. Dismissed entries are stored in a module-scope ref<Set<string>> in app.ts and automatically pruned to the intersection of currently-firing messages on each output change, so a message that stops being emitted and later re-fires will show fresh rather than staying silently suppressed.

  • The dismissedInfoMessages singleton and the pruning watch in app.ts are well-encapsulated; the prune condition (filtered.length !== current.size) is correct and avoids spurious reactive writes.
  • MainPage.vue switches the v-for key from array index to message text (a correctness improvement) and derives visibleInfoMessages via a simple computed filter against the dismissed set.
  • pnpm-workspace.yaml / pnpm-lock.yaml include incidental dependency bumps (@platforma-sdk/block-tools 2.8.1→2.9.2, @platforma-sdk/tengo-builder 2.5.29→3.0.5); the build is verified clean in the test plan.

Confidence Score: 5/5

Safe to merge — changes are UI-only, touch no BlockData schema, and the reactive prune logic is correct and well-tested.

The feature is confined to a module-scope Vue ref and a computed filter; no server state is modified. The prune watch correctly intersects dismissed entries with currently-firing messages and avoids spurious writes via the length-vs-size guard. The @update:model-value handler behaviour was already flagged in a prior review thread. Dependency bumps are incidental and the build was verified clean.

No files require special attention.

Important Files Changed

Filename Overview
ui/src/app.ts Adds module-scope dismissedInfoMessages ref and a watch inside sdkPlugin that prunes dismissed entries to the intersection with currently-firing messages; logic is correct and well-documented.
ui/src/pages/MainPage.vue Adds visibleInfoMessages computed and dismissInfoMessage function; switches v-for key from index to message text; wires closeable prop and close event on PlAlert.
pnpm-workspace.yaml Bumps @platforma-sdk/block-tools 2.8.1→2.9.2 and @platforma-sdk/tengo-builder 2.5.29→3.0.5 (major version change); incidental to the main feature, build verified in test plan.
.changeset/dismissable-info-messages-minimal.md Changeset entry marking the UI package as a minor bump with a clear description of the session-only semantics.

Sequence Diagram

sequenceDiagram
    participant WF as Workflow Output
    participant Watch as watch (app.ts)
    participant DIM as dismissedInfoMessages (ref<Set>)
    participant Comp as visibleInfoMessages (computed)
    participant UI as PlAlert (MainPage.vue)
    participant User as User

    WF->>Watch: outputs.info.messages changes
    Watch->>DIM: prune to intersection with fired set
    DIM-->>Comp: reactive update
    WF-->>Comp: reactive update
    Comp->>UI: render only non-dismissed messages

    User->>UI: clicks close button
    UI->>Comp: "@update:model-value emitted"
    Comp->>DIM: dismissInfoMessage(message) → new Set + message
    DIM-->>Comp: reactive update
    Comp->>UI: message removed from visibleInfoMessages

    Note over Watch,DIM: On next output change where message is absent,<br/>entry is pruned → future re-fire shows fresh
Loading

Reviews (3): Last reviewed commit: "docs: tighten changeset body" | Re-trigger Greptile

PlAlert on the Main tab now renders a close button. Dismissals live in a
local ref<Set<string>> — not persisted; resets when the block UI
unmounts (project close, app reload, multi-client independent). Hairpin-
safe: the only write is from PlAlert's close-button emit (user gesture).

Minimal-change variant of the persisted-dismissal approach
(paulnewling/fix/dismissable-info-messages). Useful when you want
"acknowledged for this session, fresh on reopen" semantics and don't
want to extend BlockData or add a migration step.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a session-only dismissal mechanism for info-message alerts in MainPage.vue by using a ref and computed property. A review comment correctly identifies that defining the state within <script setup> causes it to reset upon component unmounting; it is recommended to move this state to a module-level scope to ensure persistence across tab navigation.

Comment thread ui/src/pages/MainPage.vue Outdated
CI flags the 2.5.29 pin as outdated. Build is clean against the new
major version; no workflow code touched on this branch so the bump is
catalog-only.
In Vue, refs declared inside <script setup> are component-instance
state. Switching between block sections (Main, Property Relationships,
Property Distribution) unmounts MainPage and resets the dismissals.
Hoisting the ref to a module-level singleton in app.ts makes
dismissals survive in-block route changes; still resets on project
close / app reload / switching to another block.

Addresses gemini-code-assist review comment.
Earlier comment said dismissals reset on "switching to another block and
back", which is wrong. Block UIs are LRU-cached (limit 4) in the desktop
app's WebContentsMap, so the JS context survives brief navigation away.
Actual reset triggers: project close (cleanCachedBlockViews on
deleteProjectOverview), block reload (LoadBlockFrontend with recreate),
LRU eviction once the cache exceeds 4 entries, or app restart.
Dismissals are now tied to the currently-firing advisory set. When the
workflow stops emitting a previously-dismissed string (re-run with a
different input, advisory naturally goes away), it drops from the set.
A future re-fire of the same string shows fresh — no carry-over.

Implemented as watch(outputs.info.messages → local ref). Output → local
Vue ref is the sanctioned pattern in hairpin.md (state lives in the JS
context, not BlockData). No multi-client interleave risk.

Closes the stickiness gap on this branch — dismissals follow the firing
rather than persisting indefinitely.
@PaulNewling
Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread ui/src/pages/MainPage.vue
@PaulNewling
Copy link
Copy Markdown
Collaborator Author

@greptileai

@PaulNewling PaulNewling marked this pull request as ready for review May 29, 2026 15:01
CI's require-latest preflight rejects the prior pins. Build clean
against the tengo-builder major bump (3 → 4); no workflow source touched
on this branch, catalog change only.
CI's changeset-coverage check requires entries for any package whose
catalog deps changed. .workflow picks up the tengo-builder bump,
.model picks up the block-tools bump. Both are patch (no runtime change).
@PaulNewling PaulNewling added this pull request to the merge queue May 29, 2026
Merged via the queue into main with commit 150aa73 May 29, 2026
12 checks passed
@PaulNewling PaulNewling deleted the paulnewling/fix/dismissable-info-messages-minimal branch May 29, 2026 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant